{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# Building a Number‑Guessing Agent with Koog\n",
    "\n",
    "Let’s build a small but fun agent that guesses a number you’re thinking of. We’ll lean on Koog’s tool-calling to ask targeted questions and converge using a classic binary search strategy. The result is an idiomatic Kotlin Notebook that you can drop straight into docs.\n",
    "\n",
    "We’ll keep the code minimal and the flow transparent: a few tiny tools, a compact prompt, and an interactive CLI loop."
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Setup\n",
    "\n",
    "This notebook assumes:\n",
    "- You’re running in a Kotlin Notebook with Koog available.\n",
    "- The environment variable `OPENAI_API_KEY` is set. The agent uses it via `simpleOpenAIExecutor(System.getenv(\"OPENAI_API_KEY\"))`.\n",
    "\n",
    "Load the Koog kernel:"
   ]
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "%useLatestDescriptors\n",
    "%use koog"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Tools: asking targeted questions\n",
    "\n",
    "Tools are small, well-described functions the LLM can call. We’ll provide three:\n",
    "- `lessThan(value)`: “Is your number less than value?”\n",
    "- `greaterThan(value)`: “Is your number greater than value?”\n",
    "- `proposeNumber(value)`: “Is your number equal to value?” (used once the range is tight)\n",
    "\n",
    "Each tool returns a simple \"YES\"/\"NO\" string. The helper `ask` implements a minimal Y/n loop and validates input. Descriptions via `@LLMDescription` help the model select tools correctly."
   ]
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "import ai.koog.agents.core.tools.annotations.Tool\n",
    "\n",
    "class GuesserTool : ToolSet {\n",
    "\n",
    "    @Tool\n",
    "    @LLMDescription(\"Asks the user if his number is STRICTLY less than a given value.\")\n",
    "    fun lessThan(\n",
    "        @LLMDescription(\"A value to compare the guessed number with.\") value: Int\n",
    "    ): String = ask(\"Is your number less than $value?\", value)\n",
    "\n",
    "    @Tool\n",
    "    @LLMDescription(\"Asks the user if his number is STRICTLY greater than a given value.\")\n",
    "    fun greaterThan(\n",
    "        @LLMDescription(\"A value to compare the guessed number with.\") value: Int\n",
    "    ): String = ask(\"Is your number greater than $value?\", value)\n",
    "\n",
    "    @Tool\n",
    "    @LLMDescription(\"Asks the user if his number is EXACTLY equal to the given number. Only use this tool once you've narrowed down your answer.\")\n",
    "    fun proposeNumber(\n",
    "        @LLMDescription(\"A value to compare the guessed number with.\") value: Int\n",
    "    ): String = ask(\"Is your number equal to $value?\", value)\n",
    "\n",
    "    fun ask(question: String, value: Int): String {\n",
    "        print(\"$question [Y/n]: \")\n",
    "        val input = readln()\n",
    "        println(input)\n",
    "\n",
    "        return when (input.lowercase()) {\n",
    "            \"\", \"y\", \"yes\" -> \"YES\"\n",
    "            \"n\", \"no\" -> \"NO\"\n",
    "            else -> {\n",
    "                println(\"Invalid input! Please, try again.\")\n",
    "                ask(question, value)\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Tool Registry\n",
    "\n",
    "Expose your tools to the agent. We also add a built‑in `SayToUser` tool so the agent can surface messages directly to the user."
   ]
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "val toolRegistry = ToolRegistry {\n",
    "    tool(SayToUser)\n",
    "    tools(GuesserTool())\n",
    "}"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Agent configuration\n",
    "\n",
    "A short, tool‑forward system prompt is all we need. We’ll suggest a binary search strategy and keep `temperature = 0.0` for stable, deterministic behavior. Here we use OpenAI’s reasoning model `GPT4oMini` for crisp planning."
   ]
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "val agent = AIAgent(\n",
    "    executor = simpleOpenAIExecutor(System.getenv(\"OPENAI_API_KEY\")),\n",
    "    llmModel = OpenAIModels.Reasoning.GPT4oMini,\n",
    "    systemPrompt = \"\"\"\n",
    "            You are a number guessing agent. Your goal is to guess a number that the user is thinking of.\n",
    "            \n",
    "            Follow these steps:\n",
    "            1. Start by asking the user to think of a number between 1 and 100.\n",
    "            2. Use the less_than and greater_than tools to narrow down the range.\n",
    "                a. If it's neither greater nor smaller, use the propose_number tool.\n",
    "            3. Once you're confident about the number, use the propose_number tool to check if your guess is correct.\n",
    "            4. If your guess is correct, congratulate the user. If not, continue guessing.\n",
    "            \n",
    "            Be efficient with your guessing strategy. A binary search approach works well.\n",
    "        \"\"\".trimIndent(),\n",
    "    temperature = 0.0,\n",
    "    toolRegistry = toolRegistry\n",
    ")"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Run it\n",
    "\n",
    "- Think of a number between 1 and 100.\n",
    "- Type `start` to begin.\n",
    "- Answer the agent’s questions with `Y`/`Enter` for yes or `n` for no. The agent should zero in on your number in ~7 steps."
   ]
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "import kotlinx.coroutines.runBlocking\n",
    "\n",
    "println(\"Number Guessing Game started!\")\n",
    "println(\"Think of a number between 1 and 100, and I'll try to guess it.\")\n",
    "println(\"Type 'start' to begin the game.\")\n",
    "\n",
    "val initialMessage = readln()\n",
    "runBlocking {\n",
    "    agent.run(initialMessage)\n",
    "}"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## How it works\n",
    "\n",
    "- The agent reads the system prompt and plans a binary search.\n",
    "- On each iteration it calls one of your tools: `lessThan`, `greaterThan`, or (when certain) `proposeNumber`.\n",
    "- The helper `ask` collects your Y/n input and returns a clean \"YES\"/\"NO\" signal back to the model.\n",
    "- When it gets confirmation, it congratulates you via `SayToUser`."
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Extend it\n",
    "\n",
    "- Change the range (e.g., 1..1000) by tweaking the system prompt.\n",
    "- Add a `between(low, high)` tool to reduce calls further.\n",
    "- Swap models or executors (e.g., use an Ollama executor and a local model) while keeping the same tools.\n",
    "- Persist guesses or outcomes to a store for analytics."
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## Troubleshooting\n",
    "\n",
    "- Missing key: ensure `OPENAI_API_KEY` is set in your environment.\n",
    "- Kernel not found: make sure `%useLatestDescriptors` and `%use koog` executed successfully.\n",
    "- Tool not called: confirm the `ToolRegistry` includes `GuesserTool()` and the names in the prompt match your tool functions."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Kotlin",
   "language": "kotlin",
   "name": "kotlin"
  },
  "language_info": {
   "name": "kotlin",
   "version": "2.2.20-Beta2",
   "mimetype": "text/x-kotlin",
   "file_extension": ".kt",
   "pygments_lexer": "kotlin",
   "codemirror_mode": "text/x-kotlin",
   "nbconvert_exporter": ""
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
